package utils

import (
	"fmt"
	"time"
)

// Snowflake 雪花算法实现
type Snowflake struct {
	startTime          int64 // 开始的时间戳
	workerIdBits       int64 // 机器id占用的位数
	datacenterIdBits   int64 // 数据表述ID占用的位数
	maxWorkerId        int64 // 支持的最大机器id，结果是31
	maxDatacenterId    int64 // 支持的最大数据标识id，结果是31
	sequenceBits       int64 // 序列在id中占的位数
	workerIdShift      int64 // 机器ID向左移12位
	datacenterIdShift  int64 // 数据标识id向左移17位(12+5)
	timestampLeftShift int64 // 时间截向左移22位(5+5+12)
	sequenceMask       int64 // 生成序列的掩码，这里为4095 (0b111111111111=0xfff=4095)

	workerId      int64 // 工作机器ID(0~31)
	datacenterId  int64 // 数据中心ID(0~31)
	sequence      int64 // 毫秒内序列(0~4095)
	lastTimestamp int64 // 上次生成ID的时间截
}

func SnowflakeWorker(workerId, datacenterId int64) *Snowflake {

	snowflake := &Snowflake{}
	snowflake.startTime = 1577808000 // 2020-01-01 00:00:00
	snowflake.workerIdBits = 5
	snowflake.datacenterIdShift = 5
	snowflake.maxWorkerId = -1 ^ (-1 << snowflake.workerIdBits)
	snowflake.maxDatacenterId = -1 ^ (-1 << snowflake.datacenterIdBits)
	snowflake.sequenceBits = 12
	snowflake.workerIdShift = snowflake.sequenceBits
	snowflake.datacenterIdShift = snowflake.sequenceBits + snowflake.workerIdBits
	snowflake.timestampLeftShift = snowflake.sequenceBits + snowflake.workerIdBits + snowflake.datacenterIdBits
	snowflake.sequenceMask = -1 ^ (-1 << snowflake.sequenceBits)
	snowflake.lastTimestamp = 1

	if workerId > snowflake.maxWorkerId || workerId < 0 {
		panic(fmt.Sprintf("worker Id can't be greater than %d or less than 0", snowflake.maxWorkerId))
	}
	if datacenterId > snowflake.maxDatacenterId || datacenterId < 0 {
		panic(fmt.Sprintf("datacenter Id can't be greater than %d or less than 0", snowflake.maxDatacenterId))
	}

	snowflake.workerId = workerId
	snowflake.datacenterId = datacenterId
	return snowflake
}

// NextId 获取ID
func (s *Snowflake) NextId() int64 {
	nowTime := time.Now().Unix()
	if nowTime < s.lastTimestamp {
		panic(fmt.Sprintf("Clock moved backwards.  Refusing to generate id for %d milliseconds", s.lastTimestamp-nowTime))
	}
	if s.lastTimestamp == nowTime {
		s.sequence = (s.sequence + 1) & s.sequenceMask
		if s.sequence == 0 {
			for time.Now().Unix() < s.lastTimestamp {
				nowTime = time.Now().Unix()
			}
		}
	} else {
		s.sequence = 0
	}
	s.lastTimestamp = nowTime
	return ((nowTime - s.startTime) << s.timestampLeftShift) |
		(s.datacenterId << s.datacenterIdShift) |
		(s.workerId << s.workerIdShift) |
		s.sequence
}
